#pragma once

#include "base.h"
#include "../impl/s2.h"

namespace mdtx::online::updater
{
    template <typename T>
    class var : public updater_unary2<T>,
                public impl::var<T>
    {
        using base = updater_unary2<T>;
        using ibase = impl::var<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        ptrdiff_t ddof;

        explicit var(ptrdiff_t ddof)
            : ibase(),
              ddof(ddof)
        {
        }

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            T val{};
            if (this->s2 > 0 && this->n > ddof)
            {
                val = this->s2 / (this->n - ddof);
            }
            return tuple_out(this->m, val);
        }
    };

    template <typename T>
    class var_vw : public updater_weighted2<T>,
                   public impl::var_vw<T>
    {
        using base = updater_weighted2<T>;
        using ibase = impl::var_vw<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in), std::get<1>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in), std::get<1>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            T w_adj = impl::rel_weight(this->wsum, this->w2sum), val{};
            if (this->s2 > 0 && w_adj > 0)
            {
                val = this->s2 / w_adj;
            }
            return tuple_out(this->m, val);
        }
    };

    template <typename T>
    class var_ew : public updater_unary2<T>,
                   public impl::var_ew<T>
    {
        using base = updater_unary2<T>;
        using ibase = impl::var_ew<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::ibase;
        using ibase::insert_impl;
        using ibase::reset_impl;

        explicit var_ew(ptrdiff_t period)
            : ibase(T(2) / (period + 1))
        {
        }

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->m, this->s2);
        }
    };

    template <typename T>
    class var_evw : public updater_weighted2<T>,
                    public impl::var_evw<T>
    {
        using base = updater_weighted2<T>;
        using ibase = impl::var_evw<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in), std::get<1>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->m, this->s2);
        }
    };
}
